package service

import (
	"bufio"
	"fmt"
	"github.com/gogf/gf/container/gmap"
	"github.com/gogf/gf/crypto/gaes"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
	"github.com/gogf/gf/os/gfile"
	"github.com/gogf/gf/os/grpool"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/text/gregex"
	"github.com/gogf/gf/text/gstr"
	"github.com/gogf/gf/util/gconv"
	"io"
	"os"
	"sync"
	"time"
)

type m3u8 struct {
	httpPool            *grpool.Pool
	httpClient          *ghttp.Client
	store               *gmap.StrStrMap
	storePath           string
	data                *gmap.StrStrMap
	aesDecodeFileSuffix string
}

var M3u8 = m3u8{
	httpPool:            grpool.New(12),
	httpClient:          ghttp.NewClient().SetBrowserMode(true).SetAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36").SetTimeout(30 * time.Second),
	store:               gmap.NewStrStrMap(true),
	storePath:           "D:\\video\\teleplay",
	data:                nil,
	aesDecodeFileSuffix: "s",
}

// Run 执行m3u8任务
func (m *m3u8) Run(data *gmap.StrStrMap) {
	m.data = data
	fmt.Println(gtime.Datetime(), "m3u8 down start")
	m.DownloadM3u8()
	fmt.Println(gtime.Datetime(), "m3u8 down end")
	m.ParseProgress2Ts()
}

func (m *m3u8) SetStore(store *gmap.StrStrMap) *m3u8 {
	m.store = store
	return m
}

// ParseProgress2Ts 解析m3u8文件并下载ts文件-单集同时下载多个ts
func (m *m3u8) ParseProgress2Ts() {
	wait := &sync.WaitGroup{}
	m.store.Iterator(func(title string, path string) bool {
		//检查该集是否完成下载
		if !gfile.IsEmpty(m.storePath+"\\"+title+".mp4") && gfile.Size(m.storePath+"\\"+title+".mp4") > 20*1024*1024 {
			fmt.Println(gtime.Datetime(), m.storePath+"\\"+title+".mp4", "is download")
			return true
		}
		wait.Add(1)
		fmt.Println(gtime.Datetime(), title, "start")
		group := sync.WaitGroup{}
		aesKey := ""
		iv := "0000000000000000"
		advertisement := false
		var tsPathList []string
		//按行读取m3u8文件
		_ = gfile.ReadLines(gstr.Replace(path, "/", "\\"), func(text string) error {
			if gstr.Contains(text, "#EXT-X-DISCONTINUITY") {
				//碰到广告
				if advertisement == false {
					//控制跳过广告ts
					advertisement = true
				} else {
					//跳过所有广告继续解析
					advertisement = false
				}
			}
			if advertisement {
				return nil
			}
			if gstr.Contains(text, "#EXT-X-KEY:") {
				//碰到加密
				//fmt.Println("碰到加密:", text)
				//匹配key的url地址
				keyUrl, _ := gregex.MatchString(`URI="(.*)"`, text)
				//g.Dump(keyUrl)
				res, _ := m.httpClient.Get(keyUrl[1])
				aesKey = res.ReadAllString()
				//fmt.Println(aesKey)
				_ = res.Close()
				return nil
			}
			if gstr.Contains(text, "https://") {
				split := gstr.Split(text, "/")
				tsPath := m.storePath + "\\ts\\" + title + "\\" + split[len(split)-1]
				tsPathList = append(tsPathList, tsPath)
				//检查文件是否存在,已存在则跳过
				if !gfile.IsEmpty(tsPath) {
					fmt.Println(gtime.Datetime(), tsPath, "is exist")
					return nil
				}
				group.Add(1)
				_ = m.httpPool.Add(func() {
					retryCount := 0
				retry:
					res, err := m.httpClient.Get(text)
					if err != nil {
						//重试
						retryCount++
						//fmt.Println(title + ":" + text + "重试次数:" + gconv.String(retryCount))
						goto retry
					}
					body := res.ReadAllString()
					_ = res.Close()
					if body == "" {
						//重试
						retryCount++
						//fmt.Println(title + ":" + text + "重试次数:" + gconv.String(retryCount))
						goto retry
					}
					file, _ := gfile.Create(tsPath)
					_, _ = file.Write(m.TsByteAesDecode(body, aesKey, iv))
					_ = file.Close()
					fmt.Println(gtime.Datetime(), tsPath, "down end")
					group.Done()
				})
			}
			return nil
		})
		g.Dump(title, "ts length:", len(tsPathList))
		group.Wait()
		if len(tsPathList) > 0 {
			go m.MergeTsWait(title, tsPathList, wait)
		} else {
			wait.Done()
		}
		fmt.Println(gtime.Datetime(), title, "end")
		return true
	})
	wait.Wait()
}

// Parse 解析m3u8文件并下载ts文件-多集同时下载
func (m *m3u8) Parse() {
	group := sync.WaitGroup{}
	for title, path := range m.store.Map() {
		group.Add(1)
		t := title
		p := path
		_ = m.httpPool.Add(func() {
			fmt.Println(t + "开始执行ts下载")
			//定义aes解密key与iv
			aesKey := ""
			iv := "0000000000000000"
			advertisement := false
			var tsPathList []string
			//按行读取m3u8文件
			_ = gfile.ReadLines(gstr.Replace(p, "/", "\\"), func(text string) error {
				if gstr.Contains(text, "#EXT-X-DISCONTINUITY") {
					//碰到广告
					if advertisement == false {
						//控制跳过广告ts
						advertisement = true
					} else {
						//跳过所有广告继续解析
						advertisement = false
					}
				}
				if advertisement {
					return nil
				}
				if gstr.Contains(text, "#EXT-X-KEY:") {
					//碰到加密
					//fmt.Println("碰到加密:", text)
					//匹配key的url地址
					keyUrl, _ := gregex.MatchString(`URI="(.*)"`, text)
					//g.Dump(keyUrl)
					res, _ := m.httpClient.Get(keyUrl[1])
					aesKey = res.ReadAllString()
					//fmt.Println(aesKey)
					_ = res.Close()
					return nil
				}
				if gstr.Contains(text, "https://") {
					retryCount := 0
				retry:
					res, err := m.httpClient.Get(text)
					if err != nil {
						//重试
						retryCount++
						//fmt.Println(t + ":" + text + "重试次数:" + gconv.String(retryCount))
						goto retry
					}
					body := res.ReadAllString()
					_ = res.Close()
					if body == "" {
						//重试
						retryCount++
						//fmt.Println(t + ":" + text + "重试次数:" + gconv.String(retryCount))
						goto retry
					}
					split := gstr.Split(text, "/")
					tsPath := m.storePath + "\\ts\\" + t + "\\" + split[len(split)-1]
					file, _ := gfile.Create(tsPath)
					_, _ = file.Write(m.TsByteAesDecode(body, aesKey, iv))
					_ = file.Close()
					tsPathList = append(tsPathList, tsPath)
					return nil
				}
				return nil
			})
			m.MergeTs(t, tsPathList)
			g.Dump(t, gtime.Datetime())
			group.Done()
		})
	}
	group.Wait()
}
func (m *m3u8) MergeTs(title string, list []string) {
	fmt.Println(gtime.Datetime(), title, "merge start")
	for i := len(list) - 1; i > 0; i-- {
		//合并文件
		a, _ := os.OpenFile(list[i-1], os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0x002)
		b, _ := os.Open(list[i])
		reader := bufio.NewReader(b)
		_, _ = reader.WriteTo(a)
		_ = a.Close()
		_ = b.Close()
		remove := os.Remove(list[i])
		if remove != nil {
			fmt.Println(remove)
		}
	}
	fmt.Println(gtime.Datetime(), title, "merge end")
}

func (m *m3u8) MergeTsWait(title string, list []string, wait *sync.WaitGroup) {
	fmt.Println(gtime.Datetime(), title, "merge start")
	for i := len(list) - 1; i > 0; i-- {
		//合并文件
		a, _ := os.OpenFile(list[i-1], os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0x002)
		b, _ := os.Open(list[i])
		reader := bufio.NewReader(b)
		_, _ = reader.WriteTo(a)
		_ = a.Close()
		_ = b.Close()
		remove := os.Remove(list[i])
		if remove != nil {
			fmt.Println(remove)
		}
	}
	_ = gfile.Copy(list[0], m.storePath+"\\"+title+".mp4")
	fmt.Println(gtime.Datetime(), title, "merge end")
	wait.Done()
}

// TsByteAesDecode 解密ts字节
func (m *m3u8) TsByteAesDecode(data, key, iv string) []byte {
	decrypt, _ := gaes.Decrypt(gconv.Bytes(data), gconv.Bytes(key), gconv.Bytes(iv))
	return decrypt
}

// TsAesDecode 解密ts文件
func (m *m3u8) TsAesDecode(path, key, iv string) {
	decrypt, _ := gaes.Decrypt(gfile.GetBytes(path), gconv.Bytes(key), gconv.Bytes(iv))
	file, _ := gfile.Create(path + m.aesDecodeFileSuffix)
	_, _ = file.Write(decrypt)
	_ = file.Close()
}

// DownloadM3u8 执行m3u8下载任务
func (m *m3u8) DownloadM3u8() {
	group := sync.WaitGroup{}
	for title, url := range m.data.Map() {
		t := title
		u := url
		fileSavePath := m.storePath + "/m3u8/" + t + ".m3u8"
		if !gfile.IsEmpty(fileSavePath) {
			fmt.Println(gtime.Datetime(), fileSavePath, "is exist")
			m.store.Set(t, fileSavePath)
		} else {
			group.Add(1)
			_ = m.httpPool.Add(func() {
				res, err := m.httpClient.Get(u)
				if err != nil {
					fmt.Println(err)
					return
				}
				defer res.Close()
				file, err := gfile.Create(fileSavePath)
				if err != nil {
					fmt.Println(err)
					return
				}
				defer file.Close()
				_, err = io.Copy(file, res.Body)
				if err != nil {
					fmt.Println(err)
					return
				}
				fmt.Println(t + "m3u8下载完成:" + fileSavePath)
				m.store.Set(t, fileSavePath)
				group.Done()
			})
		}
	}
	group.Wait()
}
